import math
import uuid

from common.exchanges.contants import ContratType, STABLE_COINS
from common.exchanges.entities import Future, IndexPair
from common.exchanges.manager import ExchangeServiceManager
from common.models.account import Account, TradeTask
from common.utils.tools import format_num, str2float
from web.common.error_code import ErrorCode
from web.executors.multi_leg_trader import Leg, TradeInfo, TradeMode
from web.service.account import AccountService


class TradeService:

    @classmethod
    def create_hedge_trade_task(cls, user, account: Account, params) -> TradeTask:
        pub_service = ExchangeServiceManager.pub_factory(account.exchange)
        long_instrument = params.get('long_instrument')
        short_instrument = params.get('short_instrument')
        if not long_instrument or not short_instrument or long_instrument == short_instrument:
            raise ErrorCode.PARAMS_ERROR.to_error('long instrument or short instrument')
        long_inst_info = pub_service.instrument_info(inst=long_instrument, kind=ContratType.Future)
        short_inst_info = pub_service.instrument_info(inst=short_instrument, kind=ContratType.Future)

        if not isinstance(long_inst_info, Future) or not isinstance(short_inst_info, Future):
            raise ErrorCode.PARAMS_ERROR.to_error('long instrument or short instrument')
        if long_inst_info.base_currency != short_inst_info.base_currency:
            raise ErrorCode.PARAMS_ERROR.to_error('long instrument or short instrument')

        total_size = str2float(params.get('total_size'), 0)
        size_per_order = str2float(params.get('size_per_order'), 0)
        if total_size <= 0:
            raise ErrorCode.PARAMS_ERROR.to_error('total_size')
        if size_per_order <= 0:
            raise ErrorCode.PARAMS_ERROR.to_error('size_per_order')

        min_order_size = min(total_size, size_per_order)

        for inst_info in [long_inst_info, short_inst_info]:

            if inst_info.reversed:
                index_price = ExchangeServiceManager.pub().index_price(
                    IndexPair(base=inst_info.base_currency, quote=inst_info.counter_currency)
                ).price
                min_trade_size = round((inst_info.min_trade_size * inst_info.contract_multi) / index_price, 4)
            else:
                min_trade_size = inst_info.min_trade_size * inst_info.contract_multi
            min_order_size = format_num(min_order_size, min_trade_size)
            if min_order_size < min_trade_size:
                raise ErrorCode.MIN_TRADE_SIZE_ERROR.to_error('min_trade_size')

        # 检验account是否有效
        service = ExchangeServiceManager.pri_factory(
            account.exchange, AccountService.account2auth_info(account), cache=False
        )
        service.check_auth()

        total_step = math.ceil(total_size / size_per_order)
        long_order_data = Leg(
            instrument=long_instrument, total_amount=total_size, direction='buy',
            kind=ContratType.Future, step_amount=size_per_order,
        )
        short_order_data = Leg(
            instrument=short_instrument, total_amount=total_size, direction='sell',
            kind=ContratType.Future, step_amount=size_per_order,
        )
        trade_data = TradeInfo(
            legs=[long_order_data, short_order_data], is_balance=True, total_step=total_step
        )
        task = TradeTask(
            user_id=user.id, account_id=account.id, exchange=account.exchange, currency=long_inst_info.base_currency,
            type=TradeTask.TaskType.FutureSwap, params=params, data=trade_data.serialize(), id=str(uuid.uuid4())
        )

        return task

    @classmethod
    def create_balance_task(cls, user, account: Account, params) -> TradeTask:
        pub_service = ExchangeServiceManager.pub_factory(account.exchange)
        instrument = params.get('instrument')
        inst_info = pub_service.instrument_info(inst=instrument, kind=ContratType.Future)
        if not isinstance(inst_info, Future) or not isinstance(inst_info, Future):
            raise ErrorCode.PARAMS_ERROR.to_error('instrument')

        if inst_info.quote_currency not in STABLE_COINS:
            raise ErrorCode.PARAMS_ERROR.to_error('instrument')

        target_delta = str2float(params.get('target_delta'))
        if target_delta is None:
            raise ErrorCode.PARAMS_ERROR.to_error('target_delta')

        currency = inst_info.base_currency
        # 获取币种account_summary
        account_summary = AccountService().get_account_summary(account)

        positions = account_summary['positions']
        currency_positions = [position for position in positions if position['currency'] == currency]
        if not currency_positions:
            raise ErrorCode.PARAMS_ERROR.to_error(f'can not get {currency} delta info')

        delta = currency_positions[0]['delta']
        if abs(delta) < abs(target_delta):
            raise ErrorCode.PARAMS_ERROR.to_error(f'target delta')

        need_trade_delta = target_delta - delta

        if not need_trade_delta:
            raise ErrorCode.PARAMS_ERROR.to_error('当前delta无需对冲')

        total_step = 1

        leg = Leg(
            instrument=instrument, total_amount=abs(need_trade_delta), direction='sell' if need_trade_delta < 0 else 'buy',
            kind=ContratType.Future,
        )
        trade_data = TradeInfo(
            legs=[leg], is_balance=True, total_step=total_step, trade_mode=TradeMode.Middle
        )
        task = TradeTask(
            user_id=user.id, account_id=account.id, exchange=account.exchange, currency=currency,
            type=TradeTask.TaskType.DeltaHedge, params=params, data=trade_data.serialize(), id=str(uuid.uuid4())
        )
        return task

